package nz.ac.massey.cs.stats.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Statistic {

    public final int count;
    public final double sum;
    public final double max;
    public final double min;
    public final double avg;
    public final double std;
    public final double median;
    public final double Q1;
    public final double Q3;
    public final double minWithoutOutliers;
    public final double maxWithoutOutliers;
    public final double[] outliers;
    public final double[] data;

    private Statistic(Collection<? extends Number> collection) {
        if(collection.isEmpty()){
            count=0;
            sum=max=min=avg=std=median=Q1=Q3=minWithoutOutliers=maxWithoutOutliers=0;
            data=outliers=new double[]{};
        }else{
            double sum = 0;
            double ssx = 0;

            double[] a = sorted(collection);
            //System.out.println(Arrays.toString(a).replace(',', '\n'));
            count = a.length;
            median = medianOf(a, 0, count - 1);
            Q1 = medianOf(a, 0, (count / 2) - 1);
            Q3 = medianOf(a, (count / 2), count - 1);
            min = a[0];
            max = a[count - 1];

            double IRQ = Q3 - Q1;
            int s = 0, e = 0;


            for (double d : a) {
                sum += d;
            }
            this.sum = sum;
            
            avg = sum / count;

            for (int i = 0; i < count; i++) {
                if (a[i] < Q1 - 1.5 * IRQ) {
                    s++;
                } else if (a[i] > Q3 + 1.5 * IRQ) {
                    e++;
                }
            }

            minWithoutOutliers = a[s];
            maxWithoutOutliers = a[a.length - (e + 1)];

            outliers = new double[s + e];

            for (int i = 0; i < count; i++) {
                if (s > 0 && a[i] < minWithoutOutliers) {
                    outliers[--s] = a[i];
                } else if (e > 0 && a[i] > maxWithoutOutliers) {
                    outliers[outliers.length - e--] = a[i];
                }
                ssx += Math.pow(a[i] - avg, 2);
            }

            std = Math.sqrt(ssx / count);
            data = a;
        }
        
    }

    public static Statistic of(Collection<? extends Number> C) {
        return new Statistic(C);
    }

    public static Statistic of(double[]data) {
        Collection<Double> C = new ArrayList<Double>();
        for(double d: data) C.add(d);
        return new Statistic(C);
    }

    @Override
    public String toString() {
        return String.format(
                "Count: %d\nMax: %f\nMin: %f\nAvg: %f\nStd: %f,\nQ1: %f,\nMedian/Q2: %f,\nQ3: %f",
                count, max, min, avg, std, Q1, median, Q3);
    }

    public static double medianOf(double[] a, int start, int end) {
        //System.out.printf("%d---->%d\n",start,end);

        int l = end - start;
        if (l < 1) {
            //throw new IllegalArgumentException("median value of a empty list? It makes no sense," + start + ":" + end);
            return a[start];
        }
        int index = l / 2;
        double median = a[start + index];

        if ((index & 1) == 0) {
            median = (median + a[start + index + 1]) / 2.0;
        }

        return median;
    }

    public static double[] sorted(Collection<? extends Number> collection) {
        List<Number> C = new ArrayList<Number>(collection);
        Collections.sort(C, new Comparator<Number>() {
            @Override
            public int compare(Number a, Number b) {
                return a.doubleValue() < b.doubleValue() ? -1 : 1;
            }
        });
        double[] a = new double[C.size()];
        for (int i = 0; i < a.length; i++) {
            a[i] = C.get(i).doubleValue();
        }
        return a;
    }
}
